// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/pepper/plugin_object.h"

#include <stdint.h>

#include <memory>

#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
#include "content/renderer/pepper/pepper_try_catch.h"
#include "content/renderer/pepper/plugin_module.h"
#include "content/renderer/pepper/v8_var_converter.h"
#include "gin/arguments.h"
#include "gin/converter.h"
#include "gin/function_template.h"
#include "gin/handle.h"
#include "gin/interceptor.h"
#include "gin/object_template_builder.h"
#include "gin/public/gin_embedders.h"
#include "ppapi/c/dev/ppb_var_deprecated.h"
#include "ppapi/c/dev/ppp_class_deprecated.h"
#include "ppapi/c/pp_resource.h"
#include "ppapi/c/pp_var.h"
#include "ppapi/shared_impl/ppapi_globals.h"
#include "ppapi/shared_impl/resource_tracker.h"
#include "ppapi/shared_impl/var.h"
#include "ppapi/shared_impl/var_tracker.h"

using ppapi::PpapiGlobals;
using ppapi::ScopedPPVar;
using ppapi::ScopedPPVarArray;
using ppapi::StringVar;
using ppapi::Var;

namespace content {

namespace {

    const char kInvalidValueException[] = "Error: Invalid value";

} // namespace

// PluginObject ----------------------------------------------------------------

PluginObject::~PluginObject()
{
    if (instance_) {
        ppp_class_->Deallocate(ppp_class_data_);
        instance_->RemovePluginObject(this);
    }
}

// static
gin::WrapperInfo PluginObject::kWrapperInfo = { gin::kEmbedderNativeGin };

// static
PluginObject* PluginObject::FromV8Object(v8::Isolate* isolate,
    v8::Local<v8::Object> v8_object)
{
    PluginObject* plugin_object;
    if (!v8_object.IsEmpty() && gin::ConvertFromV8(isolate, v8_object, &plugin_object)) {
        return plugin_object;
    }
    return NULL;
}

// static
PP_Var PluginObject::Create(PepperPluginInstanceImpl* instance,
    const PPP_Class_Deprecated* ppp_class,
    void* ppp_class_data)
{
    V8VarConverter var_converter(instance->pp_instance(),
        V8VarConverter::kAllowObjectVars);
    PepperTryCatchVar try_catch(instance, &var_converter, NULL);
    // If the V8 context is empty, we may be in the process of tearing down the
    // frame and may not have a valid isolate (in particular due to re-entrancy).
    // We shouldn't try to call gin::CreateHandle.
    if (try_catch.GetContext().IsEmpty()) {
        ppp_class->Deallocate(ppp_class_data);
        return PP_MakeUndefined();
    }
    gin::Handle<PluginObject> object = gin::CreateHandle(instance->GetIsolate(),
        new PluginObject(instance, ppp_class, ppp_class_data));
    ScopedPPVar result = try_catch.FromV8(object.ToV8());
    DCHECK(!try_catch.HasException());
    return result.Release();
}

v8::Local<v8::Value> PluginObject::GetNamedProperty(
    v8::Isolate* isolate,
    const std::string& identifier)
{
    if (!instance_) {
        std::string error = "Property " + identifier + " does not exist.";
        isolate->ThrowException(
            v8::Exception::ReferenceError(gin::StringToV8(isolate, error)));
        return v8::Local<v8::Value>();
    }
    ScopedPPVar identifier_var(ScopedPPVar::PassRef(),
        StringVar::StringToPPVar(identifier));
    return GetPropertyOrMethod(instance_->GetIsolate(), identifier_var.get());
}

bool PluginObject::SetNamedProperty(v8::Isolate* isolate,
    const std::string& identifier,
    v8::Local<v8::Value> value)
{
    if (!instance_) {
        std::string error = "Property " + identifier + " does not exist.";
        isolate->ThrowException(
            v8::Exception::ReferenceError(gin::StringToV8(isolate, error)));
        return false;
    }
    ScopedPPVar identifier_var(ScopedPPVar::PassRef(),
        StringVar::StringToPPVar(identifier));
    V8VarConverter var_converter(instance_->pp_instance(),
        V8VarConverter::kAllowObjectVars);
    PepperTryCatchV8 try_catch(instance_, &var_converter, isolate);

    bool has_property = ppp_class_->HasProperty(ppp_class_data_, identifier_var.get(),
        try_catch.exception());
    if (try_catch.ThrowException())
        return false;

    if (!has_property)
        return false;

    ScopedPPVar var = try_catch.FromV8(value);
    if (try_catch.ThrowException())
        return false;

    ppp_class_->SetProperty(ppp_class_data_, identifier_var.get(), var.get(),
        try_catch.exception());

    // If the plugin threw an exception, then throw a V8 version of it to
    // JavaScript. Either way, return true, because we successfully dispatched
    // the call to the plugin.
    try_catch.ThrowException();
    return true;
}

std::vector<std::string> PluginObject::EnumerateNamedProperties(
    v8::Isolate* isolate)
{
    std::vector<std::string> result;
    if (!instance_) {
        std::string error = "Plugin object deleted";
        isolate->ThrowException(
            v8::Exception::ReferenceError(gin::StringToV8(isolate, error)));
        return result;
    }

    V8VarConverter var_converter(instance_->pp_instance(),
        V8VarConverter::kAllowObjectVars);
    PepperTryCatchV8 try_catch(instance_, &var_converter, isolate);

    PP_Var* name_vars;
    uint32_t count = 0;
    ppp_class_->GetAllPropertyNames(ppp_class_data_, &count, &name_vars,
        try_catch.exception());
    ScopedPPVarArray scoped_name_vars(
        ScopedPPVarArray::PassPPBMemoryAllocatedArray(), name_vars, count);

    if (try_catch.ThrowException())
        return result;

    for (uint32_t i = 0; i < count; ++i) {
        StringVar* string_var = StringVar::FromPPVar(name_vars[i]);
        if (string_var) {
            result.push_back(string_var->value());
        } else {
            try_catch.ThrowException(kInvalidValueException);
            result.clear();
            return result;
        }
    }

    return result;
}

void PluginObject::InstanceDeleted()
{
    instance_ = NULL;
}

PluginObject::PluginObject(PepperPluginInstanceImpl* instance,
    const PPP_Class_Deprecated* ppp_class,
    void* ppp_class_data)
    : gin::NamedPropertyInterceptor(instance->GetIsolate(), this)
    , instance_(instance)
    , ppp_class_(ppp_class)
    , ppp_class_data_(ppp_class_data)
    , template_cache_(instance->GetIsolate())
    , weak_factory_(this)
{
    instance_->AddPluginObject(this);
}

gin::ObjectTemplateBuilder PluginObject::GetObjectTemplateBuilder(
    v8::Isolate* isolate)
{
    return Wrappable<PluginObject>::GetObjectTemplateBuilder(isolate)
        .AddNamedPropertyInterceptor();
}

v8::Local<v8::Value> PluginObject::GetPropertyOrMethod(v8::Isolate* isolate,
    PP_Var identifier_var)
{
    if (!instance_)
        return v8::Local<v8::Value>();

    V8VarConverter var_converter(instance_->pp_instance(),
        V8VarConverter::kAllowObjectVars);
    PepperTryCatchV8 try_catch(instance_, &var_converter, isolate);
    bool has_property = ppp_class_->HasProperty(ppp_class_data_, identifier_var,
        try_catch.exception());
    if (try_catch.ThrowException())
        return v8::Local<v8::Value>();

    if (has_property) {
        ScopedPPVar result_var(ScopedPPVar::PassRef(),
            ppp_class_->GetProperty(ppp_class_data_, identifier_var,
                try_catch.exception()));
        if (try_catch.ThrowException())
            return v8::Local<v8::Value>();

        v8::Local<v8::Value> result = try_catch.ToV8(result_var.get());
        if (try_catch.ThrowException())
            return v8::Local<v8::Value>();

        return result;
    }

    bool has_method = identifier_var.type == PP_VARTYPE_STRING && ppp_class_->HasMethod(ppp_class_data_, identifier_var, try_catch.exception());
    if (try_catch.ThrowException())
        return v8::Local<v8::Value>();

    if (has_method) {
        const std::string& identifier = StringVar::FromPPVar(identifier_var)->value();
        return GetFunctionTemplate(isolate, identifier)->GetFunction();
    }

    return v8::Local<v8::Value>();
}

void PluginObject::Call(const std::string& identifier,
    gin::Arguments* args)
{
    if (!instance_)
        return;

    V8VarConverter var_converter(instance_->pp_instance(),
        V8VarConverter::kAllowObjectVars);
    PepperTryCatchV8 try_catch(instance_, &var_converter, args->isolate());
    ScopedPPVar identifier_var(ScopedPPVar::PassRef(),
        StringVar::StringToPPVar(identifier));
    ScopedPPVarArray argument_vars(args->Length());

    for (uint32_t i = 0; i < argument_vars.size(); ++i) {
        v8::Local<v8::Value> arg;
        if (!args->GetNext(&arg)) {
            NOTREACHED();
        }

        argument_vars.Set(i, try_catch.FromV8(arg));
        if (try_catch.ThrowException())
            return;
    }

    // For the OOP plugin case we need to grab a reference on the plugin module
    // object to ensure that it is not destroyed courtesy an incoming
    // ExecuteScript call which destroys the plugin module and in turn the
    // dispatcher.
    scoped_refptr<PluginModule> ref(instance_->module());

    ScopedPPVar result_var(ScopedPPVar::PassRef(),
        ppp_class_->Call(ppp_class_data_, identifier_var.get(),
            argument_vars.size(), argument_vars.get(),
            try_catch.exception()));
    if (try_catch.ThrowException())
        return;

    v8::Local<v8::Value> result = try_catch.ToV8(result_var.get());
    if (try_catch.ThrowException())
        return;

    args->Return(result);
}

v8::Local<v8::FunctionTemplate> PluginObject::GetFunctionTemplate(
    v8::Isolate* isolate,
    const std::string& name)
{
    v8::Local<v8::FunctionTemplate> function_template = template_cache_.Get(name);
    if (!function_template.IsEmpty())
        return function_template;
    function_template = gin::CreateFunctionTemplate(
        isolate, base::Bind(&PluginObject::Call, weak_factory_.GetWeakPtr(), name));
    template_cache_.Set(name, function_template);
    return function_template;
}

} // namespace content
